package domain.parsers;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import domain.grammar.ExtendedGrammar;
import domain.grammar.ExtendedNonTerminalSymbol;
import domain.grammar.ExtendedProduction;
import domain.grammar.ExtendedProductionSet;
import domain.grammar.ExtendedTerminalSymbol;
import domain.regularExpression.RegularExpression;


/*
 * Grammar productions used by this parser
 * 
 * production1: 	G --> P G1
 * production2: 	G1 --> lamda
 * production3:		G1 --> G
 * production4: 	P --> n: PD1 ;
 * 
 */

public class InputGrammarLL1Parser implements ExtendedGrammarParser{
	private RegularExpressionParser _reParser;
	private Set<ExtendedNonTerminalSymbol> _nonTerminals = new HashSet<ExtendedNonTerminalSymbol>();
	private Set<ExtendedTerminalSymbol> _terminals = new HashSet<ExtendedTerminalSymbol>();
	private ExtendedNonTerminalSymbol _startSymbol;
	private Set<ExtendedProduction> _productions = new HashSet<ExtendedProduction>();
	private String _notation;
	
	
	public InputGrammarLL1Parser(){
		_reParser = new RegularExpressionParser();
		_notation = "Notation:\n" +
				"'n' represents a non terminal Symbol and must be a capital character\n"+
				"'t' represents a terminal Symbol and must be a non-capital character\n";
	}
	
	public synchronized ExtendedGrammar parse(File aFile) throws ParseException, IOException{
		return parse(new FileInputStream(aFile));
	}
	
	public synchronized ExtendedGrammar parse(FileInputStream fis) throws ParseException, IOException{
		InputStreamReader isr = new InputStreamReader(fis);
		LL1Reader ll1Reader = new LL1Reader(isr);
		parse_G_production(ll1Reader,true);
		
		Iterator<ExtendedProduction> itProd = _productions.iterator();
		while(itProd.hasNext()){
			ExtendedProduction anExtProd = itProd.next();
			_nonTerminals.addAll(anExtProd.get_rightSide().getSetOfElemOfClass(ExtendedNonTerminalSymbol.class));
			_terminals.addAll(anExtProd.get_rightSide().getSetOfElemOfClass(ExtendedTerminalSymbol.class));
		}
		return new ExtendedGrammar(_nonTerminals,_terminals,new ExtendedProductionSet(_productions),_startSymbol);


	}
	

	
	private void parse_G_production(LL1Reader ll1Reader, boolean isFirstCall) throws ParseException, IOException{
		Set<Character> production1_DirectrixSymbs = new HashSet<Character>();
		production1_DirectrixSymbs.add('n');
		if(production1_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			parse_P_production(ll1Reader,isFirstCall);
			parse_G1_production(ll1Reader);
		}else{
			List<Character> expected = new Vector<Character>();
			expected.addAll(production1_DirectrixSymbs);
			throw new ParseException(	ll1Reader.actualLine(), 
														ll1Reader.actualColumn(), 
														(char)ll1Reader.getCurrentChar(), 
														expected,
														_notation);
		}
	}
	
	private void parse_G1_production(LL1Reader ll1Reader) throws IOException, ParseException {
		
		if((ll1Reader.getCurrentChar()==10)||(ll1Reader.getCurrentChar()==13)){
			ll1Reader.readNext();
		}
			
		if(ll1Reader.getCurrentChar()==-1){
			return;
		}else{

			Set<Character> production3_DirectrixSymbs = new HashSet<Character>();
			production3_DirectrixSymbs.add('n');
			if(production3_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
				this.parse_G_production(ll1Reader, false);
			}else{
				List<Character> expected = new Vector<Character>();
				expected.addAll(production3_DirectrixSymbs);
				throw new ParseException(	ll1Reader.actualLine(), 
															ll1Reader.actualColumn(), 
															(char)ll1Reader.getCurrentChar(), 
															expected,
															_notation);
			}
		}
		
	}

	private ExtendedProduction parse_P_production(LL1Reader ll1Reader,boolean isFirstCall) throws ParseException, IOException{
		Set<Character> production4_DirectrixSymbs = new HashSet<Character>();
		production4_DirectrixSymbs.add('n');
		if(production4_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			Character ntName = (char)ll1Reader.getCurrentChar();
			ExtendedNonTerminalSymbol ntSymb = new ExtendedNonTerminalSymbol(ntName.toString());
			if(isFirstCall){
				_startSymbol=ntSymb;
			}
			_nonTerminals.add(ntSymb);
			
			int doubleDot = ll1Reader.readNext();
			if(doubleDot == (int)':'){
				
				ll1Reader.readNext();
				RegularExpression aRegularExpression = _reParser.parse(ll1Reader);
				ExtendedProduction extProd = new ExtendedProduction(ntSymb,aRegularExpression);
				_productions.add(extProd);
				if(ll1Reader.getCurrentChar()!=(int)';'){
					List<Character> expected = new Vector<Character>();
					expected.add(';');
					throw new ParseException(	ll1Reader.actualLine(), 
																ll1Reader.actualColumn(), 
																(char)ll1Reader.getCurrentChar(), 
																expected,
																_notation);
				}
				ll1Reader.readNext();
				return extProd;
			}else{
				List<Character> expected = new Vector<Character>();
				expected.add(':');
				throw new ParseException(	ll1Reader.actualLine(), 
															ll1Reader.actualColumn(), 
															(char)ll1Reader.getCurrentChar(), 
															expected,
															_notation);
			}

		}else{
			List<Character> expected = new Vector<Character>();
			expected.addAll(production4_DirectrixSymbs);
			throw new ParseException(	ll1Reader.actualLine(), 
														ll1Reader.actualColumn(), 
														(char)ll1Reader.getCurrentChar(), 
														expected,
														_notation);
		}
	}
	
	



	private Character transformTerminalAndNonTerminal(Character aChar){
		int asciiCode = aChar;
		if(65 <= asciiCode && asciiCode <=90){
			return 'n';
		}else if(97 <= asciiCode && asciiCode <=122){
			return 't';
		}else{
			return aChar;
		}
	}
}
